package cn.shop.front.service.impl;

import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.shop.base.MyServiceImpl;
import cn.shop.front.entity.req.WithdrawQueryReq;
import cn.shop.front.service.*;
import cn.shop.front.util.IpUtil;
import cn.shop.transcation.alipay.entity.AlipayAccount;
import cn.shop.transcation.backSetting.entity.BackSetting;
import cn.shop.transcation.backSetting.mapper.BackSettingMapper;
import cn.shop.transcation.bankcard.entity.BankCard;
import cn.shop.transcation.detail.entity.FundDetail;
import cn.shop.transcation.detail.mapper.FundDetailMapper;
import cn.shop.transcation.frontUserModule.FrontUser;
import cn.shop.transcation.order.entity.OrderReward;
import cn.shop.transcation.order.mapper.OrderRewardMapper;
import cn.shop.transcation.promotionsModule.PrizeRule;
import cn.shop.transcation.promotionsModule.Promotions;
import cn.shop.transcation.promotionsModule.mapper.PrizeRuleMapper;
import cn.shop.transcation.withdraw.entity.Withdraw;
import cn.shop.transcation.withdraw.mapper.WithdrawMapper;
import cn.shop.utils.ApiResult;
import cn.shop.utils.PageUtil;
import cn.shop.utils.Putif;
import cn.shop.utils.constant.PromotionsConstant;
import cn.shop.utils.enums.ProcessState;
import cn.shop.utils.enums.WithdrawAccountType;
import cn.shop.utils.enums.WithdrawChannel;
import cn.shop.utils.enums.WithdrawSolution;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static cn.shop.utils.constant.SystemSettingConstant.*;

/**
 * @author : 李一涵
 * @date : Created in 2022-2-25 16:48
 * @description :
 */
@Slf4j
@Service
public class WithdrawServiceImpl extends MyServiceImpl<WithdrawMapper, Withdraw> implements WithdrawService {

	private final static Logger LOG = LoggerFactory.getLogger(WithdrawServiceImpl.class);

	@Resource
	private FrontUserService frontUserService;

	@Resource
	private BankCardService bankCardService;

	@Resource
	private AlipayService alipayService;

	@Resource
	private FundDetailMapper fundDetailMapper;

	@Resource
	private BackSettingMapper settingMapper;

	@Resource
	private PromotionsService promotionsService;
	@Resource
	private PrizeRuleMapper prizeRuleMapper;
	@Resource
	private OrderRewardMapper rewardMapper;

	@Override
	@Transactional(rollbackFor = Exception.class)
	public ApiResult<Map<String, Object>> withdraw(long userId, BigDecimal amount, long id, String type, String password, String domain, WithdrawChannel channel) {
		FrontUser user = frontUserService.getById(userId);
		if (user == null) {
			return ApiResult.fail("用户不存在");
		}
		if (user.getBalance().compareTo(amount) < 0) {
			return ApiResult.fail("可提现金额不足");
		}
		if (!DigestUtil.bcryptCheck(password, user.getTransactionPassword())) {
			return ApiResult.fail("交易密码不正确");
		}
		Withdraw withdraw = new Withdraw();
		withdraw.setUserId(userId);
		withdraw.setAmount(amount);
		withdraw.setBalance(user.getBalance().subtract(amount));
		withdraw.setState(ProcessState.UNTREATED);
		withdraw.setType(2);
		withdraw.setChannel(channel);
		BigDecimal charges;
		//rate 百分比提成， single 单笔扣款
		BigDecimal rate = BigDecimal.ZERO, single = BigDecimal.ZERO;
		List<BackSetting> settings =  settingMapper.selectList(Wrappers.<BackSetting>lambdaQuery()
				.in(BackSetting::getKey, P_BANK_WITHDRAW_SHARE, P_BANK_WITHDRAW_SUB, P_ALI_WITHDRAW_SHARE, P_ALI_WITHDRAW_SUB));
		Map<String, String> map = settings.stream().collect(Collectors.toMap(BackSetting::getKey, BackSetting::getValue));
		if (StrUtil.equals(type, "1")) {
			BankCard card = bankCardService.getById(id);
			if (card == null) {
				return ApiResult.fail("银行卡信息不存在");
			}
			//百分比提成计算share1
			String v = map.get(P_BANK_WITHDRAW_SHARE);
			if (NumberUtil.isNumber(v)) {
				charges = new BigDecimal(v).divide(new BigDecimal(100), 4, RoundingMode.CEILING);
				if (charges.compareTo(BigDecimal.ZERO) > 0) {
					rate = amount.multiply(charges);
				}
			}
			//计算单笔扣款
			String v2 = map.get(P_BANK_WITHDRAW_SUB);
			if (NumberUtil.isNumber(v)) {
				charges = new BigDecimal(v2);
				if (charges.compareTo(BigDecimal.ZERO) > 0) {
					single = charges;
				}
			}
			withdraw.setActualAmount(amount.subtract(rate).subtract(single));
			withdraw.setCharges(rate.add(single));
			withdraw.setAccount(card.getCardNo());
			withdraw.setAccountType(WithdrawAccountType.BANK_PAYMENT);
			withdraw.setBankName(card.getBankName());
			withdraw.setBankAccountName(card.getName());
		} else {
			AlipayAccount alipay = alipayService.getById(id);
			if (alipay == null) {
				return ApiResult.fail("支付宝信息不存在");
			}
			//百分比提成计算share1
			String v = map.get(P_ALI_WITHDRAW_SHARE);
			if (NumberUtil.isNumber(v)) {
				charges = new BigDecimal(v).divide(new BigDecimal(100), 4, RoundingMode.CEILING);
				if (charges.compareTo(BigDecimal.ZERO) > 0) {
					rate = amount.multiply(charges);
				}
			}
			//计算单笔扣款
			String v2 = map.get(P_ALI_WITHDRAW_SUB);
			if (NumberUtil.isNumber(v)) {
				charges = new BigDecimal(v2);
				if (charges.compareTo(BigDecimal.ZERO) > 0) {
					single = charges;
				}
			}
			withdraw.setActualAmount(amount.subtract(rate).subtract(single));
			withdraw.setCharges(rate.add(single));
			withdraw.setAccount(alipay.getAccount());
			withdraw.setAccountType(WithdrawAccountType.ALIPAY_PAYMENT);
			withdraw.setBankAccountName(alipay.getName());
		}
		withdraw.setChargeRate(rate);
		withdraw.setChargeSingle(single);
		withdraw.setIdCard(user.getIdentityCard());
		withdraw.setPhone(user.getPhone());
		BigDecimal actualReward = BigDecimal.ZERO;
		if (withdraw.getChannel() == WithdrawChannel.APP) {
			Promotions promotions = promotionsService.getOne(Wrappers.<Promotions>lambdaQuery()
					.eq(Promotions::getKey, PromotionsConstant.APP_REWARD));
			if (promotions != null
					&& promotions.getEnable()
					&& NumberUtil.isGreater(promotions.getPrizePool(), BigDecimal.ZERO)) {
				actualReward = getActualReward(withdraw);
				withdraw.setActualAmount(withdraw.getActualAmount().add(actualReward));
			}
		}
		boolean success = save(withdraw);
		if (!success) {
			LOG.warn("保存提现数据出错， {}", withdraw);
			throw new RuntimeException("提现失败");
		}
		if (NumberUtil.isGreater(actualReward, BigDecimal.ZERO)) {
			reward(withdraw, actualReward);
		}
		FrontUser update = new FrontUser();
		update.setId(userId);
		LambdaUpdateWrapper<FrontUser> updates = Wrappers.lambdaUpdate(update)
				.setSql("balance = balance - " + amount)
				.eq(FrontUser::getId, userId);
		success = frontUserService.update(update, updates);
		if (!success) {
			LOG.warn("修改用户可提现金额失败, 用户号：{}， 时间：{}", userId, LocalDateTime.now());
			throw new RuntimeException("提现失败");
		}
		Long merchantId = fundDetailMapper.getMerchantIdByAlias(domain);
		FundDetail detail = new FundDetail();
		detail.setType(3);
		detail.setUid(merchantId != null ? merchantId : 0L);
		detail.setOrderId(withdraw.getId());
		detail.setPlayerId(userId);
		detail.setPlayerAccount(user.getAccount());
		detail.setPlatformShare(withdraw.getCharges());
		detail.setBalance(user.getBalance().subtract(amount));
		detail.setAmount(amount);
		detail.setAlias(domain);
		detail.setIp(IpUtil.getClientIp());
		detail.setGoods("玩家提现");
		detail.setDealTime(LocalDateTime.now());
		fundDetailMapper.insert(detail);
		return ApiResult.success("提现成功");
	}

	@Override
	public Map<String, Object> listPage(long userId, WithdrawQueryReq req) {
		Page<Withdraw> page = Page.of(req.getCurrent(), req.getSize());
		LambdaQueryWrapper<Withdraw> query = Wrappers.<Withdraw>lambdaQuery()
				.eq(Withdraw::getUserId, userId)
				.eq(req.getState() != null, Withdraw::getState, req.getState())
				.gt(req.getStartTime() != null, Withdraw::getCreateTime, req.getStartTime())
				.lt(req.getEndTime() != null, Withdraw::getCreateTime, req.getEndTime())
				.orderByDesc(Withdraw::getCreateTime);
		page = page(page, query);
		return PageUtil.formatPage(page, this::format);
	}

	@Override
	public Map<String, String> withdrawRange() {
		List<BackSetting> list = settingMapper.selectList(Wrappers.<BackSetting>lambdaQuery()
				.in(BackSetting::getKey, Arrays.asList(WITHDRAW_MIN, WITHDRAW_MAX)));
		return list.stream().collect(Collectors.toMap(BackSetting::getKey, BackSetting::getValue));
	}


	private BigDecimal getActualReward(Withdraw withdraw) {
		List<PrizeRule> rules = prizeRuleMapper.selectList(Wrappers.<PrizeRule>lambdaQuery().eq(PrizeRule::getKey, WITHDRAW_REWARD_RATE));
		if (rules.isEmpty()) { //中奖规则为控股你
			return BigDecimal.ZERO;
		}
		PrizeRule prize = PrizeRule.pay(rules);
		if (prize == null) { //未获得奖品
			return BigDecimal.ZERO;
		}
		String p = prize.getPrize();
		if (StrUtil.isBlank(p)) { //奖品内没设置值
			return BigDecimal.ZERO;
		}
		String[] s = p.split(",");
		if (s.length < 2 || !NumberUtil.isNumber(s[0]) || !NumberUtil.isNumber(s[1])) { //奖品错误
			return BigDecimal.ZERO;
		}
		BigDecimal charges = withdraw.getCharges();
		BigDecimal rateMin = charges.multiply(NumberUtil.toBigDecimal(s[0]));
		BigDecimal rateMax = charges.multiply(NumberUtil.toBigDecimal(s[1]));
		BigDecimal reward = RandomUtil.randomBigDecimal(rateMin, rateMax).setScale(2, RoundingMode.HALF_UP);
		return calculationReward(withdraw, reward);
	}

	private void reward(Withdraw withdraw, BigDecimal actualReward) {
		if (NumberUtil.isLessOrEqual(actualReward, BigDecimal.ZERO)) {
			return;
		}
		OrderReward r = new OrderReward();
		r.setAmount(actualReward);
		r.setUserId(withdraw.getUserId());
		r.setOrderId(withdraw.getId());
		r.setType(1);
		rewardMapper.insert(r);
	}

	private BigDecimal calculationReward(Withdraw record, BigDecimal reward) {
		boolean isSuccess = promotionsService.update(Wrappers.<Promotions>lambdaUpdate()
				.setSql("prize_pool = prize_pool - " + reward)
				.eq(Promotions::getKey, PromotionsConstant.APP_REWARD)
				.gt(Promotions::getPrizePool, BigDecimal.ZERO));
		BigDecimal actualReward = BigDecimal.ZERO;
		if (isSuccess) {
			//扣款成功
			//如果扣款完是负数
			//则不给奖励
			//如果扣款完负数比奖励低，则把剩余的给了
			//不是负数则正常给
			Promotions promotions = promotionsService.getOne(Wrappers.<Promotions>lambdaQuery()
					.eq(Promotions::getKey, PromotionsConstant.APP_REWARD));
			if (promotions == null) {
				return actualReward;
			}
			if (NumberUtil.isLess(promotions.getPrizePool(), BigDecimal.ZERO)) { //奖池金额小于0
				if (!NumberUtil.isLess(promotions.getPrizePool().abs(), reward)) { //奖池剩余金额的绝对值小于手续费，需要补回奖池里的金额
					actualReward = reward.subtract(promotions.getPrizePool().abs());
					log.info("用户{} 提现{} 实际到账金额{}，从活动奖池奖励{}，奖池剩余不足{}，实际奖励{}",
							record.getUserId(), record.getAmount(), record.getActualAmount(), reward, promotions.getPrizePool(), actualReward);
				}
			} else {
				log.info("用户{} 提现{} 实际到账金额{}，从活动奖池中补回手续费{}，奖池剩余{}",
						record.getUserId(), record.getAmount(), record.getActualAmount(), reward, promotions.getPrizePool());
				actualReward = reward;
			}
		}
		return actualReward;
	}
	private Map<String, Object> format(Withdraw withdraw) {
		return Putif.start()
				.anyway("time", withdraw.getCreateTime())
				.anyway("amount", withdraw.getAmount())
				.anyway("charges", withdraw.getCharges())
				.anyway("balance", withdraw.getBalance())
				.anyway("channel", withdraw.getAccountType().getDescription())
				.anyway("state", withdraw.getState() == ProcessState.ABNORMAL && withdraw.getSolution() == WithdrawSolution.SEND_BACK
						? "已退回"
						: withdraw.getState().getType())
				.get();
	}

}
